package jsr292.weaver.opt;

import jsr292.weaver.Wrapper;
import jsr292.weaver.opt.OptimizerVarAllocator.Var;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class Converters {
    private static int hash(Class<?> clazz) {
	String name = clazz.getName();
	if (name.length() < 2)
	    return 0;
	return name.charAt(0) << 8 + name.charAt(1);
    }

    public static void convertOnStackReturnValue(MethodVisitor mv, Class<?> from, Class<?> to) {
	if (from == to)
	    return;

	if (from == void.class) {
	    convertOnStackFromVoid(mv, to);
	    return;
	}

	if (to == void.class) {
	    Type fromType = Type.getType(from);
	    if (fromType.getSize() == 1) {
		mv.visitInsn(Opcodes.POP);
	    } else {
		mv.visitInsn(Opcodes.POP2);
	    }
	    return;
	}
	
	convertOnStackParameter(mv, from, to);
    }
    
    public static void convertParameter(MethodVisitor mv, OptimizerVarAllocator allocator, int index, Class<?> from, Class<?> to) {
	if (from == to)
	    return;
	
	Var var = allocator.getVar(index);
	var.load(mv);
	
	convertOnStackParameter(mv, from, to);
	
	Var newVar = allocator.replaceVarType(index, Type.getType(to));
	newVar.store(mv);
    }

    public static void convertOnStackParameter(MethodVisitor mv, Class<?> from, Class<?> to) {
	if (from.isPrimitive()) {
	    if (to.isPrimitive()) {
		// primitive to primitive
		convertOnStackPrimitive(mv, from, to);

	    } else {
		// primitive to object
		Wrapper wrapper = Wrapper.getWrapper(to);
		if (wrapper == null) {
		    // 'to' is not a wrapper, so 'from' must be a wrapper
		    wrapper = Wrapper.getWrapper(from);
		    if (wrapper == null)
			throw new AssertionError("no conversion from " + from
				+ " to " + to);
		} else {
		    // convert primitive before boxing
		    if (from != wrapper.primitiveClass) {
			convertOnStackPrimitive(mv, wrapper.primitiveClass, to);
		    }
		}

		// box
		String wrapperClassName = wrapper.wrapperClassName;
		mv.visitMethodInsn(Opcodes.INVOKESTATIC,
			wrapperClassName, "valueOf", wrapper.boxingDesc);
	    }
	} else {
	    if (to.isPrimitive()) {
		// object to primitive

		Wrapper wrapper = Wrapper.getWrapper(from);
		if (wrapper == null) {
		    wrapper = Wrapper.getWrapper(to);
		    if (wrapper == null) {
			throw new AssertionError("no conversion from " + from
				+ " to " + to);
		    }
		    
		    // cast
		    mv.visitTypeInsn(Opcodes.CHECKCAST, wrapper.wrapperClassName);
		}

		Class<?> wrapperPrimitiveClass = wrapper.primitiveClass;

		// unbox
		mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
			wrapper.wrapperClassName, wrapper.unboxingMethodName,
			wrapper.unboxingDesc);

		// convert primitive
		if (wrapperPrimitiveClass != to) {
		    convertOnStackPrimitive(mv, wrapperPrimitiveClass, to);
		}

	    } else {
		// object to object
		if (!to.isInterface()) {
		    
		    if (to.isAssignableFrom(from))
			return;
		    
		    // cast
		    mv.visitTypeInsn(Opcodes.CHECKCAST,
			    Type.getInternalName(to));
		}
	    }
	}
    }

    private static void convertOnStackPrimitive(MethodVisitor mv, Class<?> from, Class<?> to) {
	// assert from==Object.class || from.isPrimitive() || from==void.class;
	// assert to==Object.class || to.isPrimitive() || to==void.class;

	// System.out.println("from "+from+" to "+to+" value "+value);

	if (from == to)
	    return;

	switch (hash(from)) {
	case 'b' << 8 + 'o': // boolean
	case 'b' << 8 + 'y': // byte
	case 'c' << 8 + 'h': // char
	case 'i' << 8 + 'n': // int
	case 's' << 8 + 'h': // short
	    convertOnStackFromInteger(mv, to);
	    return;
	case 'd' << 8 + 'o': // double
	    convertOnStackDouble(mv, to);
	    return;
	case 'f' << 8 + 'l': // float
	    convertOnStackFloat(mv, to);
	    return;
	case 'l' << 8 + 'o': // long
	    convertOnStackFromLong(mv, to);
	    return;
	default:
	    throw new AssertionError("no encoder from " + from);
	}
    }

    private static void convertOnStackDouble(MethodVisitor mv, Class<?> to) {
	switch (hash(to)) {
	case 'b' << 8 + 'o':// boolean
	    mv.visitInsn(Opcodes.DCONST_0);
	    mv.visitInsn(Opcodes.DCMPL);
	    break;
	case 'b' << 8 + 'y': // byte
	    mv.visitInsn(Opcodes.D2I);
	    mv.visitInsn(Opcodes.I2B);
	    break;
	case 's' << 8 + 'h': // short
	    mv.visitInsn(Opcodes.D2I);
	    mv.visitInsn(Opcodes.I2S);
	    break;
	case 'c' << 8 + 'h': // char
	    mv.visitInsn(Opcodes.D2I);
	    mv.visitInsn(Opcodes.I2C);
	    break;
	case 'f' << 8 + 'l': // float
	    mv.visitInsn(Opcodes.D2F);
	    break;
	case 'i' << 8 + 'n': // int
	    mv.visitInsn(Opcodes.D2I);
	    break;
	case 'l' << 8 + 'o': // long
	    mv.visitInsn(Opcodes.D2L);
	    break;
	default: // any object
	    throw new AssertionError("no encoder for " + to);
	}
    }

    private static void convertOnStackFloat(MethodVisitor mv, Class<?> to) {
	switch (hash(to)) {
	case 'b' << 8 + 'o': // boolean
	    mv.visitInsn(Opcodes.FCONST_0);
	    mv.visitInsn(Opcodes.FCMPL);
	    break;
	case 'b' << 8 + 'y': // byte
	    mv.visitInsn(Opcodes.F2I);
	    mv.visitInsn(Opcodes.I2B);
	    break;
	case 's' << 8 + 'h': // short
	    mv.visitInsn(Opcodes.F2I);
	    mv.visitInsn(Opcodes.I2S);
	    break;
	case 'c' << 8 + 'h': // char
	    mv.visitInsn(Opcodes.F2I);
	    mv.visitInsn(Opcodes.I2C);
	    break;
	case 'd' << 8 + 'o': // double
	    mv.visitInsn(Opcodes.F2D);
	    break;
	case 'i' << 8 + 'n': // int
	    mv.visitInsn(Opcodes.F2I);
	    break;
	case 'l' << 8 + 'o': // long
	    mv.visitInsn(Opcodes.F2L);
	    break;
	default:
	    throw new AssertionError("no encoder for " + to);
	}
    }

    private static void convertOnStackFromLong(MethodVisitor mv, Class<?> to) {
	switch (hash(to)) {
	case 'b' << 8 + 'o': // boolean
	    mv.visitInsn(Opcodes.LCONST_0);
	    mv.visitInsn(Opcodes.LCMP);
	    break;
	case 'b' << 8 + 'y': // byte
	    mv.visitInsn(Opcodes.L2I);
	    mv.visitInsn(Opcodes.I2B);
	    break;
	case 'c' << 8 + 'h': // char
	    mv.visitInsn(Opcodes.L2I);
	    mv.visitInsn(Opcodes.I2C);
	    break;
	case 's' << 8 + 'h': // short
	    mv.visitInsn(Opcodes.L2I);
	    mv.visitInsn(Opcodes.I2S);
	    break;
	case 'd' << 8 + 'o': // double
	    mv.visitInsn(Opcodes.L2D);
	    break;
	case 'f' << 8 + 'l': // float
	    mv.visitInsn(Opcodes.L2F);
	    break;
	case 'i' << 8 + 'n': // int
	    mv.visitInsn(Opcodes.L2I);
	    break;
	default:
	    throw new AssertionError("no encoder for " + to);
	}
    }

    private static void convertOnStackFromInteger(MethodVisitor mv, Class<?> to) {
	switch (hash(to)) {
	case 'b' << 8 + 'o': // boolean
	    // do nothing
	    break;
	case 'b' << 8 + 'y': // byte
	    mv.visitInsn(Opcodes.I2B);
	    break;
	case 'c' << 8 + 'h': // char
	    mv.visitInsn(Opcodes.I2C);
	    break;
	case 's' << 8 + 'h': // short
	    mv.visitInsn(Opcodes.I2S);
	    break;
	case 'l' << 8 + 'o': // long
	    mv.visitInsn(Opcodes.I2L);
	    break;
	case 'f' << 8 + 'l': // float
	    mv.visitInsn(Opcodes.I2F);
	    break;
	case 'd' << 8 + 'o': // double
	    mv.visitInsn(Opcodes.I2D);
	    break;
	default:
	    throw new AssertionError("no encoder for " + to);
	}
    }

    private static void convertOnStackFromVoid(MethodVisitor mv, Class<?> to) {
	switch (hash(to)) {
	case 'b' << 8 + 'o': // boolean
	case 'b' << 8 + 'y': // byte
	case 's' << 8 + 'h': // short
	case 'c' << 8 + 'h': // char
	case 'i' << 8 + 'n': // int
	    mv.visitInsn(Opcodes.ICONST_0);
	    return;
	case 'd' << 8 + 'o': // double
	    mv.visitInsn(Opcodes.DCONST_0);
	    return;
	case 'f' << 8 + 'l': // float
	    mv.visitInsn(Opcodes.FCONST_0);
	    return;
	case 'l' << 8 + 'o': // long
	    mv.visitInsn(Opcodes.LCONST_0);
	    return;
	default: // objects
	    mv.visitInsn(Opcodes.ACONST_NULL);
	    return;
	}
    }
}
